//-------------------------------------------------------------------------------
///
/// \author	Yang Shen
/// \date		April 20, 2014
///
/// \brief		Real-time Soft Shadow
///
//-------------------------------------------------------------------------------
#include "glHeader.h"

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <Windows.h>
#include <time.h>
#include <math.h>

#include "Image.h"
#include "Scene.h"
#include "Shader.h"
#include "Global.h"

//-------------------------------------------------------------------------------
/// global variables
//-------------------------------------------------------------------------------
Scene scene;
Shader shader;

//-------------------------------------------------------------------------------
/// mouse operation
//-------------------------------------------------------------------------------

int window_size_w  = 800;
int window_size_h = 800;
 
float viewAngle1 = 0, viewAngle2 = 0, viewTransZ = 0;
int mouseMode;
int mouseX, mouseY;

enum MouseModes {
    MOUSE_MODE_NONE,
    MOUSE_MODE_VIEW_ROTATE,
    MOUSE_MODE_VIEW_ZOOM,
	MOUSE_MODE_MOVE_LIGHT
};

//-------------------------------------------------------------------------------
bool pauseCubeRotation = false;
int cubeRotateX = 0;
int cubeRotateY = 1;
int cubeRotateZ = 0;


//-------------------------------------------------------------------------------
/// shadow map
//-------------------------------------------------------------------------------
GLuint sm_tex, sm_fbo; //, sm_renderbuffer;
int sm_size = 128;


//-------------------------------------------------------------------------------
/// light
//-------------------------------------------------------------------------------
float light_radius = 0.1f;


//-------------------------------------------------------------------------------
/// function list
//-------------------------------------------------------------------------------

void cleanup();
//void idle();
void display();
void reshape(int w, int h);
void mouse(int button, int state, int x, int y);
void mouseMove(int x, int y);
void keyboard(unsigned char key, int x , int y);	

void computeFPS();

void init();
void initShadowMap(int size = 128);

void setTextureMatrix();

//void drawObject();
void drawTeapot();
void drawCube();
void drawTerrain();
void drawLight();

//-------------------------------------------------------------------------------
/// main
//-------------------------------------------------------------------------------

int main(int argc, char **argv) {

    glutInit(&argc,argv); 
 
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );
    if (glutGet(GLUT_SCREEN_WIDTH) > 0 && glutGet(GLUT_SCREEN_HEIGHT) > 0) {
        glutInitWindowPosition( (glutGet(GLUT_SCREEN_WIDTH) - window_size_w)/2, (glutGet(GLUT_SCREEN_HEIGHT) - window_size_h)/2 );
    } else {
		glutInitWindowPosition( 50, 50 );
	}
    glutInitWindowSize(window_size_w, window_size_h);
 
    glutCreateWindow("Real-time Soft Shadow");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMotionFunc(mouseMove);
	glutKeyboardFunc(keyboard);

	init();

    glutMainLoop();

	atexit(cleanup);
 
    return 0;
}

//-------------------------------------------------------------------------------

void cleanFBO() {

	// delete resources
   glDeleteTextures(1, &sm_tex);
//   glDeleteRenderbuffersEXT(1, &sm_renderbuffer);
   // bind 0, which means render to back buffer, as a result, fb is unbound
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
   glDeleteFramebuffersEXT(1, &sm_fbo);

}

void cleanup() {
	
	cleanFBO();

}

//-------------------------------------------------------------------------------

void initShadowMap(int size) {
	
	//////////////////////////////////////////////////////
	/// clean up
	//////////////////////////////////////////////////////

	if(sm_fbo) cleanFBO();

	//////////////////////////////////////////////////////
	/// resolution
	//////////////////////////////////////////////////////

	sm_size = size;

	//////////////////////////////////////////////////////
	/// depth texture and Frame Buffer Object
	//////////////////////////////////////////////////////

//*	// Depth texture. Slower than a depth buffer, but you can sample it later in your shader
	glGenTextures(1, &sm_tex);
	glBindTexture(GL_TEXTURE_2D, sm_tex);
	glTexImage2D(GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT16, sm_size, sm_size, 0,GL_DEPTH_COMPONENT, GL_FLOAT, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	// FBO
	sm_fbo = 0;
	glGenFramebuffersEXT(1, &sm_fbo);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, sm_fbo);
	glFramebufferTextureEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, sm_tex, 0);
 
	glDrawBuffer(GL_NONE); // No color buffer is drawn to.
 
	// Always check that our framebuffer is ok
	if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
		std::cout<<"GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n";

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // */


	/*

	////////////////

	// color buffer
	glGenTextures(1, &sm_tex);
	glBindTexture(GL_TEXTURE_2D, sm_tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, sm_size, sm_size, 0, GL_RGB, GL_FLOAT, 0); // TODO: to support floating point in opengl
	glBindTexture(GL_TEXTURE_2D, 0);

	// depth buffer
	glGenRenderbuffersEXT(1, &sm_renderbuffer);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, sm_renderbuffer);
	glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, sm_size, sm_size);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

	// FBO
	sm_fbo = 0;
	glGenFramebuffersEXT(1, &sm_fbo);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, sm_fbo);
	// attach color buffer
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D, sm_tex, 0); 
	// attach depth buffer
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, sm_renderbuffer);
//	glDrawBuffer(GL_NONE); // No color buffer is drawn to
//	glReadBuffer(GL_NONE);
	// Always check that our framebuffer is ok
	if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
		std::cout<<"GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n";		
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);  //*/
}

void init() {

	// init glew
	GLenum success = glewInit();
	if (GLEW_OK != success) exit(0);

	// 
	initShadowMap();
	scene.Init();
	shader.Init();
}

//-------------------------------------------------------------------------------

void computeFPS() {

	static int lastUpdate = 0;
	static int frames = 0;
	char buffer[100];

//	glutPostRedisplay(); // calls your display callback function
//	glutSwapBuffers();

	int currentTime = glutGet( GLUT_ELAPSED_TIME );
	frames++;

	// is the time difference between lastUpdate and current time > one second ( 1000 ms )?
	if ( currentTime - lastUpdate >= 1000 ) {

		sprintf( buffer, "Real-time Soft Shadow: %d fps, %d * %d shadowmap", frames, sm_size, sm_size );
		glutSetWindowTitle( buffer );
		frames = 0;
		lastUpdate = currentTime;
	}
}

//-------------------------------------------------------------------------------
void drawAxis() {

	glPushAttrib(GL_ENABLE_BIT);
	glDisable(GL_LIGHTING);
	// draw axis
	const float lineSize = 100;
    glBegin(GL_LINES);
    glColor3f(1,0,0);
    glVertex3f(0,0,0);
    glVertex3f(lineSize,0,0);
    glColor3f(0,1,0);
    glVertex3f(0,0,0);
    glVertex3f(0,lineSize,0);
    glColor3f(0,0,1);
    glVertex3f(0,0,0);
    glVertex3f(0,0,lineSize);
    glEnd();
	glPopAttrib();
}

void drawSceneDepthTest() {

	///////////////////////////////////////////////////
	/// create shadow map
	///////////////////////////////////////////////////

	glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, sm_fbo );

	// clear color and depth buffers
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearColor(1.f, 1.f, 1.f, 1.f);

	// light view -------------------------------------

	// view port	
	glViewport(0, 0, sm_size, sm_size);
	
	// projection matrix
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    float aspect = (float) sm_size / (float) sm_size;
    gluPerspective( 90, aspect, 0.02, 1000.0);

	// view matrix
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
	gluLookAt( scene.camera.eyex, scene.camera.eyey, scene.camera.eyez,
		scene.camera.centerx, scene.camera.centery, scene.camera.centerz,
		scene.camera.upx, scene.camera.upy, scene.camera.upz );
	//gluLookAt(scene.light.eyex, scene.light.eyey, scene.light.eyez, 
	//	scene.light.centerx, scene.light.centery, scene.light.centerz,
	//	scene.light.upx, scene.light.upy, scene.light.upz); 
	
	// Enable depth test
    glEnable( GL_DEPTH_TEST );

    // Set view
	glPushMatrix();
    glTranslatef( 0, 0, viewTransZ );
    glRotatef( viewAngle1, 1, 0, 0 );
    glRotatef( viewAngle2, 0, 0, 1 );
	
	// draw the depth
	if(shader.depth.linked) {
		glUseProgramObjectARB(shader.depth.program);
//		drawObject();
		drawTeapot();
		drawCube();
		drawTerrain();
		glUseProgramObjectARB(0);
	}

	glPopMatrix();


	///////////////////////////////////////////////////
	/// draw the scene
	///////////////////////////////////////////////////
	
	glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, 0 );

	// clear color and depth buffers
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearColor(1.f, 1.f, 1.f, 1.f);

	// camera view -------------------------------------

	// view port	
	glViewport(0, 0, window_size_w, window_size_h);
	
	// projection matrix
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    aspect = (float) window_size_w / (float) window_size_h;
    gluPerspective( 90, aspect, 0.02, 1000.0);

	// view matrix
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
	gluLookAt( scene.camera.eyex, scene.camera.eyey, scene.camera.eyez,
		scene.camera.centerx, scene.camera.centery, scene.camera.centerz,
		scene.camera.upx, scene.camera.upy, scene.camera.upz );
	//gluLookAt(scene.light.eyex, scene.light.eyey, scene.light.eyez, 
	//	scene.light.centerx, scene.light.centery, scene.light.centerz,
	//	scene.light.upx, scene.light.upy, scene.light.upz); 
	
	// Enable depth test
    glEnable( GL_DEPTH_TEST );

    // Set view
	glPushMatrix();
    glTranslatef( 0, 0, viewTransZ );
    glRotatef( viewAngle1, 1, 0, 0 );
    glRotatef( viewAngle2, 0, 0, 1 );

	// draw axis
	drawAxis();
	
	// draw light
	drawLight();

	// draw the depth
	if(shader.depth.linked) {
		glUseProgramObjectARB(shader.depth.program);
//		drawObject();
		drawTeapot();
		drawCube();
		drawTerrain();
		glUseProgramObjectARB(0);
	}

	glPopMatrix();

	// draw the depth map -------------------------------------
	glEnable( GL_TEXTURE_2D );
	glBindTexture(GL_TEXTURE_2D, sm_tex);
	glBegin(GL_QUADS);
			glTexCoord2f(0, 1);	glVertex3f(-1.f, 1.f, 0.f);
			glTexCoord2f(1, 1);	glVertex3f(-0.6f, 1.f, 0.f);
			glTexCoord2f(1, 0);	glVertex3f(-0.6f, 0.6f, 0.f);
			glTexCoord2f(0, 0);	glVertex3f(-1.f, 0.6f, 0.f);
	glEnd();
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable( GL_TEXTURE_2D );

	glutSwapBuffers();
}

void drawScene2() {
	
	using namespace std;

	static float depthModelView[16];
	static float depthProj[16];
	// This is matrix transform every coordinate x,y,z
	// x = x* 0.5 + 0.5 
	// y = y* 0.5 + 0.5 
	// z = z* 0.5 + 0.5 
	// Moving from unit cube [-1,1] to [0,1]  
	static const float depthBias[16] = {	
		0.5, 0.0, 0.0, 0.0, 
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
		0.5, 0.5, 0.5, 1.0 };

	///////////////////////////////////////////////////
	/// create shadow map
	///////////////////////////////////////////////////

	glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, sm_fbo );

	// clear color and depth buffers
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearColor(1.f, 1.f, 1.f, 1.f);

	// light view -------------------------------------

	// view port	
	glViewport(0, 0, sm_size, sm_size);
	
	// projection matrix
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    float aspect = (float) sm_size / (float) sm_size;
    gluPerspective( 90, aspect, 0.02, 1000.0);
	// load
	glGetFloatv(GL_PROJECTION_MATRIX, depthProj);	

	// view matrix
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
	gluLookAt( scene.camera.eyex, scene.camera.eyey, scene.camera.eyez,
		scene.camera.centerx, scene.camera.centery, scene.camera.centerz,
		scene.camera.upx, scene.camera.upy, scene.camera.upz );
	//gluLookAt(scene.light.eyex, scene.light.eyey, scene.light.eyez, 
	//	scene.light.centerx, scene.light.centery, scene.light.centerz,
	//	scene.light.upx, scene.light.upy, scene.light.upz); 

	// load
	glGetFloatv(GL_MODELVIEW_MATRIX, depthModelView);
	
	// Enable depth test
    glEnable( GL_DEPTH_TEST );

    // Set view
	glPushMatrix();
    glTranslatef( 0, 0, viewTransZ );
    glRotatef( viewAngle1, 1, 0, 0 );
    glRotatef( viewAngle2, 0, 0, 1 );
	
	// draw the depth
	if(shader.depth.linked) {
		glUseProgramObjectARB(shader.depth.program);
//		drawObject();
		drawTeapot();
		drawCube();
		drawTerrain();
		glUseProgramObjectARB(0);
	}

	glPopMatrix();

	///////////////////////////////////////////////////
	/// draw the scene
	///////////////////////////////////////////////////
	
	glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, 0 );

	// clear color and depth buffers
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearColor(1.f, 1.f, 1.f, 1.f);

	// camera view -------------------------------------

	// view port	
	glViewport(0, 0, window_size_w, window_size_h);
	
	// projection matrix
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    aspect = (float) window_size_w / (float) window_size_h;
    gluPerspective( 90, aspect, 0.02, 1000.0);

	// view matrix
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
	gluLookAt( scene.camera.eyex, scene.camera.eyey, scene.camera.eyez,
		scene.camera.centerx, scene.camera.centery, scene.camera.centerz,
		scene.camera.upx, scene.camera.upy, scene.camera.upz );
	//gluLookAt(scene.light.eyex, scene.light.eyey, scene.light.eyez, 
	//	scene.light.centerx, scene.light.centery, scene.light.centerz,
	//	scene.light.upx, scene.light.upy, scene.light.upz); 
	
	// Enable depth test
    glEnable( GL_DEPTH_TEST );

    // Set view
	glPushMatrix();
    glTranslatef( 0, 0, viewTransZ );
    glRotatef( viewAngle1, 1, 0, 0 );
    glRotatef( viewAngle2, 0, 0, 1 );

	// draw axis
	drawAxis();
	
	// draw light
	drawLight();

	// draw the depth
	/*
	if(shader.depth.linked) {
		glUseProgramObjectARB(shader.depth.program);
		drawObject();
		drawTerrain();
		glUseProgramObjectARB(0);
	}	
	//*/

	// draw the scene
//	glCullFace(GL_BACK);
	//*
	if (shader.sm.linked) {
		
		glEnable( GL_TEXTURE_2D );

		glUseProgramObjectARB(shader.sm.program);
		
		// set modelview, projection, bias matrices
		int locModelView = glGetUniformLocationARB(shader.sm.program, "depthModelView");
		glUniformMatrix4fvARB(locModelView, 1, false, depthModelView);
		int locProj = glGetUniformLocationARB(shader.sm.program, "depthProj");
		glUniformMatrix4fvARB(locProj, 1, false, depthProj);
		int locBias = glGetUniformLocationARB(shader.sm.program, "depthBias");
		glUniformMatrix4fvARB(locBias, 1, false, depthBias);

		// set shadow map texture
		glUniform1iARB(glGetUniformLocationARB(shader.sm.program, "smTex"), 0);
		glActiveTextureARB(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, sm_tex);
		
		// set object's material 
		glUniform4f(	glGetUniformLocation( shader.sm.program , "Ambient" ), 
								blue.ambient[0], blue.ambient[1], blue.ambient[2], blue.ambient[3]	);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Diffuse" ), 
							blue.diffuse[0], blue.diffuse[1], blue.diffuse[2], blue.diffuse[3]);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Specular" ), 
							blue.specular[0], blue.specular[1], blue.specular[2], blue.specular[3]);
		glUniform1f(	glGetUniformLocation( shader.sm.program , "Shininess" ), blue.shininess[0] );
		glUniform4f( glGetUniformLocation( shader.sm.program , "Emission" ), 0.22, 0.22, 0.22, 1 );

//		drawObject();
		drawTeapot();
		drawCube();
		
		// set terrain's material
		glUniform4f(	glGetUniformLocation( shader.sm.program , "Ambient" ), 
								white.ambient[0], white.ambient[1], white.ambient[2], white.ambient[3]	);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Diffuse" ), 
							white.diffuse[0], white.diffuse[1], white.diffuse[2], white.diffuse[3]);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Specular" ), 
							white.specular[0], white.specular[1], white.specular[2], white.specular[3]);
		glUniform1f(	glGetUniformLocation( shader.sm.program , "Shininess" ), white.shininess[0] );
		glUniform4f( glGetUniformLocation( shader.sm.program , "Emission" ), 0.22, 0.22, 0.22, 1 );

		drawTerrain();

		glUseProgramObjectARB(0);
		
		glBindTexture(GL_TEXTURE_2D, 0);
		glDisable( GL_TEXTURE_2D );
	}
	//*/
	glPopMatrix();

	// draw the depth map -------------------------------------
	glEnable( GL_TEXTURE_2D );
	glBindTexture(GL_TEXTURE_2D, sm_tex);
	glPushMatrix();
	glScalef(scaleScene, scaleScene, 1.f);
	glBegin(GL_QUADS);
			glTexCoord2f(0, 1);	glVertex3f(-1.f, 1.f, 0.f);
			glTexCoord2f(1, 1);	glVertex3f(-0.4f, 1.f, 0.f);
			glTexCoord2f(1, 0);	glVertex3f(-0.4f, 0.4f, 0.f);
			glTexCoord2f(0, 0);	glVertex3f(-1.f, 0.4f, 0.f);
	glEnd();
	glPopMatrix();
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable( GL_TEXTURE_2D );
	
	// swap buffers -------------------------------------
	glutSwapBuffers();	
}

void drawScene() {
	
	using namespace std;

	static float depthModelViewTerrain[16];
	static float depthModelViewTeapot[16];
	static float depthModelViewCube[16];
	static float depthProj[16];
	// This is matrix transform every coordinate x,y,z
	// x = x* 0.5 + 0.5 
	// y = y* 0.5 + 0.5 
	// z = z* 0.5 + 0.5 
	// Moving from unit cube [-1,1] to [0,1]  
	static const float depthBias[16] = {	
		0.5, 0.0, 0.0, 0.0, 
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
		0.5, 0.5, 0.5, 1.0 };

	// rotate cube
	static float cubeAngle = 0.f;
	if (!pauseCubeRotation) {
		int currentTime = glutGet( GLUT_ELAPSED_TIME );
		cubeAngle = (float)currentTime;
	}
	// scale teapot
	float scaleTeapotY = 2.f;
	float scaleTeapotX = 2.f;
	float scaleTeapotZ = 2.f;

	///////////////////////////////////////////////////
	/// create shadow map
	///////////////////////////////////////////////////

	glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, sm_fbo );

	// clear color and depth buffers
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearColor(1.f, 1.f, 1.f, 1.f);

	// light view -------------------------------------

	// view port	
	glViewport(0, 0, sm_size, sm_size);
	
	// projection matrix
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    float aspect = (float) sm_size / (float) sm_size;
	gluPerspective( 60, aspect, scene.light.nearplane, scene.light.farplane);	
	// load projection matrix
	glGetFloatv(GL_PROJECTION_MATRIX, depthProj);	
	
	// view matrix
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
	gluLookAt(scene.light.eyex, scene.light.eyey, scene.light.eyez, 
		scene.light.centerx, scene.light.centery, scene.light.centerz,
		scene.light.upx, scene.light.upy, scene.light.upz); 	
	
	// Enable depth test
    glEnable( GL_DEPTH_TEST );
	
    glEnable(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset( 2, 4 );

	// draw the depth
//	glCullFace(GL_FRONT);
	if(shader.depth.linked) {

		glUseProgramObjectARB(shader.depth.program);

		// pass in scene.light.invfarplane
		glUniform1f(	glGetUniformLocation( shader.depth.program , "scale" ), scene.light.invfarplane );

//*		// transform terrain
		glPushMatrix();
		glScalef( scaleScene, scaleScene, scaleScene );
		glPushMatrix();
		glTranslatef(0.f, -0.75f * scaleScene, 0.f);
		// load the model view matrix of Terrain
		glGetFloatv(GL_MODELVIEW_MATRIX, depthModelViewTerrain);
		// draw terrain
		drawTerrain();
		// exit transforming terrain
		glPopMatrix();
		glPopMatrix(); // */

		// transform cube
		glPushMatrix();
		glScalef( scaleScene, scaleScene, scaleScene );
		glPushMatrix();
		glTranslatef(0.f, 1.5f + 0.5f * scaleTeapotY, 0.f);	
		glRotatef( -cubeAngle, cubeRotateX, cubeRotateY, cubeRotateZ );
		if ( cubeRotateY ) {
			glScalef(0.1f, 0.1f, 4.f);
		} else {
			glScalef(0.1f, 4.f, 0.1f);
		}
		// load the model view matrix of Cubex
		glGetFloatv(GL_MODELVIEW_MATRIX, depthModelViewCube);
		// draw cube
		drawCube();
		// exit transforming cube
		glPopMatrix();
		glPopMatrix();
		/*
		cout<<"cube\t";
		for( int i = 0; i < 16; ++i ) {
			cout<<depthModelViewCube[i]<<" ";
		}
		cout<<endl<<endl; // */

//*		// transform teapot
		glPushMatrix();
		glScalef( scaleScene, scaleScene, scaleScene );
		glPushMatrix();
		glTranslatef(0.f, -0.2f + 0.5f * scaleTeapotY, 0.f);
		glRotatef(90, 0, 1, 0);
		glScalef(scaleTeapotX, scaleTeapotY, scaleTeapotZ);
		// load the model view matrix of Teapot
		glGetFloatv(GL_MODELVIEW_MATRIX, depthModelViewTeapot);
		// draw teapot
		drawTeapot();
		// exit transforming teapot
		glPopMatrix();
		glPopMatrix();  // */
		/*
		cout<<"teapot\t";
		for( int i = 0; i < 16; ++i ) {
			cout<<depthModelViewTeapot[i]<<" ";
		}
		 cout<<endl<<endl<<endl<<endl; // */

		glUseProgramObjectARB(0);
	}	

    glDisable(GL_POLYGON_OFFSET_FILL);

	//glPopMatrix();

	///////////////////////////////////////////////////
	/// draw the scene
	///////////////////////////////////////////////////
	
	glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, 0 );

	// clear color and depth buffers
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearColor(1.f, 1.f, 1.f, 1.f);

	// camera view -------------------------------------

	// view port	
	glViewport(0, 0, window_size_w, window_size_h);
	
	// projection matrix
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    aspect = (float) window_size_w / (float) window_size_h;
    gluPerspective( 90, aspect, 1.f, 100.f);

	// view matrix
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
	gluLookAt( scene.camera.eyex, scene.camera.eyey, scene.camera.eyez,
		scene.camera.centerx, scene.camera.centery, scene.camera.centerz,
		scene.camera.upx, scene.camera.upy, scene.camera.upz );
	//gluLookAt(scene.light.eyex, scene.light.eyey, scene.light.eyez, 
	//	scene.light.centerx, scene.light.centery, scene.light.centerz,
	//	scene.light.upx, scene.light.upy, scene.light.upz); 
	
	// Enable depth test
    glEnable( GL_DEPTH_TEST );

    // Set view
	glPushMatrix();
    glTranslatef( 0, 0, viewTransZ );
    glRotatef( viewAngle1, 1, 0, 0 );
    glRotatef( viewAngle2, 0, 1, 0 );

	// draw axis
	drawAxis();
	
	// draw light
	drawLight();

	// draw the scene
	glCullFace(GL_BACK);
	//*
	if (shader.sm.linked) {
		
		glEnable( GL_TEXTURE_2D );

		glUseProgramObjectARB(shader.sm.program);
		
		// get localtion of model view
		int locModelView = glGetUniformLocationARB(shader.sm.program, "depthModelView");
		// set projection, bias matrices
		int locProj = glGetUniformLocationARB(shader.sm.program, "depthProj");
		int locBias = glGetUniformLocationARB(shader.sm.program, "depthBias");

		glUniformMatrix4fvARB(locProj, 1, false, depthProj);
		glUniformMatrix4fvARB(locBias, 1, false, depthBias);

		// set shadow map texture
		glUniform1iARB(glGetUniformLocationARB(shader.sm.program, "smTex"), 0);
		glActiveTextureARB(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, sm_tex);

		// scale factor of shadow map
		glUniform1f(	glGetUniformLocation( shader.sm.program , "depthScale" ), scene.light.farplane );
		
//*		// set teapot's model view matrix
		glUniformMatrix4fvARB(locModelView, 1, false, depthModelViewTeapot);
		// set teapot's material 
		glUniform4f(	glGetUniformLocation( shader.sm.program , "Ambient" ), 
								blue.ambient[0], blue.ambient[1], blue.ambient[2], blue.ambient[3]	);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Diffuse" ), 
							blue.diffuse[0], blue.diffuse[1], blue.diffuse[2], blue.diffuse[3]);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Specular" ), 
							blue.specular[0], blue.specular[1], blue.specular[2], blue.specular[3]);
		glUniform1f(	glGetUniformLocation( shader.sm.program , "Shininess" ), blue.shininess[0] );
		glUniform4f( glGetUniformLocation( shader.sm.program , "Emission" ), 0.22, 0.22, 0.22, 1 );

		// transform teapot
		glPushMatrix();
		glScalef( scaleScene, scaleScene, scaleScene );
		glPushMatrix();
		glTranslatef(0.f, -0.2f + 0.5f * scaleTeapotY, 0.f);
		glRotatef(90, 0, 1, 0);
		glScalef( scaleTeapotX, scaleTeapotY, scaleTeapotZ );
		// draw teapot
		drawTeapot();
		// exit transforming teapot
		glPopMatrix();
		glPopMatrix();  // */
		
		
		// set cube's model view matrix
		glUniformMatrix4fvARB(locModelView, 1, false, depthModelViewCube);
		// set cube's material 
		glUniform4f(	glGetUniformLocation( shader.sm.program , "Ambient" ), 
								red.ambient[0], red.ambient[1], red.ambient[2], red.ambient[3]	);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Diffuse" ), 
							red.diffuse[0], red.diffuse[1], red.diffuse[2], red.diffuse[3]);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Specular" ), 
							red.specular[0], red.specular[1], red.specular[2], red.specular[3]);
		glUniform1f(	glGetUniformLocation( shader.sm.program , "Shininess" ), red.shininess[0] );
		glUniform4f( glGetUniformLocation( shader.sm.program , "Emission" ), 0.22, 0.22, 0.22, 1 );

		// transform cube
		glPushMatrix();
		glScalef( scaleScene, scaleScene, scaleScene );
		glPushMatrix();
		glTranslatef(0.f, 1.5f + 0.5f * scaleTeapotY, 0.f);	
		glRotatef( -cubeAngle, cubeRotateX, cubeRotateY, cubeRotateZ );
		if ( cubeRotateY ) {
			glScalef(0.1f, 0.1f, 4.f);
		} else {
			glScalef(0.1f, 4.f, 0.1f);
		}
		// draw cube
		drawCube();
		// exit transforming cube
		glPopMatrix();
		glPopMatrix();

		
//*		// set terrain's model view matrix
		glUniformMatrix4fvARB(locModelView, 1, false, depthModelViewTerrain);
		// set terrain's material
		glUniform4f(	glGetUniformLocation( shader.sm.program , "Ambient" ), 
								white.ambient[0], white.ambient[1], white.ambient[2], white.ambient[3]	);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Diffuse" ), 
							white.diffuse[0], white.diffuse[1], white.diffuse[2], white.diffuse[3]);
		glUniform4f( glGetUniformLocation( shader.sm.program , "Specular" ), 
							white.specular[0], white.specular[1], white.specular[2], white.specular[3]);
		glUniform1f(	glGetUniformLocation( shader.sm.program , "Shininess" ), white.shininess[0] );
		glUniform4f( glGetUniformLocation( shader.sm.program , "Emission" ), 0.22, 0.22, 0.22, 1 );

		// transform terrain
		glPushMatrix();
		glScalef( scaleScene, scaleScene, scaleScene );
		glPushMatrix();
		glTranslatef(0.f, -0.75f * scaleScene, 0.f);
		// draw terrain
		drawTerrain();
		// exit transforming terrain
		glPopMatrix();
		glPopMatrix(); // */

		glUseProgramObjectARB(0);
		
		glBindTexture(GL_TEXTURE_2D, 0);
		glDisable( GL_TEXTURE_2D );
	}
	glPopMatrix();

	// draw the depth map -------------------------------------

	if (shader.texture.linked) {
		
		glEnable( GL_TEXTURE_2D );

		glUseProgramObjectARB(shader.texture.program);
		
		glUniform1iARB(glGetUniformLocationARB(shader.sm.program, "Tex"), 0);
		glActiveTextureARB(GL_TEXTURE0);
		
		glBindTexture(GL_TEXTURE_2D, sm_tex);
		
		glPushMatrix();
		glScalef(scaleScene, scaleScene, 1.f);
		glBegin(GL_QUADS);
				glTexCoord2f(0, 1);	glVertex3f(-1.f, 1.f, 0.f);
				glTexCoord2f(1, 1);	glVertex3f(-0.4f, 1.f, 0.f);
				glTexCoord2f(1, 0);	glVertex3f(-0.4f, 0.4f, 0.f);
				glTexCoord2f(0, 0);	glVertex3f(-1.f, 0.4f, 0.f);
		glEnd();
		glPopMatrix();
		
		glUseProgramObjectARB( 0 );

		glBindTexture(GL_TEXTURE_2D, 0);
		glDisable( GL_TEXTURE_2D );
	}
	
	// swap buffers -------------------------------------
	glutSwapBuffers();	
}


void display() {
	
	// draw
	drawScene();
	
	// compute fps
	computeFPS();
}

void drawLight() {
	
	glPushAttrib(GL_ENABLE_BIT);

	glDisable(GL_LIGHTING);

	glPushMatrix();
	glTranslatef(scene.light.eyex, scene.light.eyey, scene.light.eyez);

	// draw light sphere
	glColor3f(1.f, 1.f, 0.f);
	glutSolidSphere(light_radius * scaleScene, 32, 32);

	glPopMatrix();

	glPopAttrib();
}

void drawCube() {
	glutSolidCube( 0.7f );
}

void drawTeapot() {
	glutSolidTeapot( 1.0f );
}

void drawTerrain() {

	glNormal3f(0.f, 1.f, 0.f);

	float y = 0.f; //-0.75f * scaleScene;
	float step = 1.f;
	float size = 50.f * scaleScene;
	for(float x = -size; x <= size; x += step) {
		for(float z = -size; z <= size; z += step) {
			glBegin(GL_QUADS);
			glVertex3f( x, y, z );
			glVertex3f( x+step, y, z );
			glVertex3f( x+step, y, z+step );
			glVertex3f( x, y, z+step );
			glEnd();
		}
	}

}
 
//-------------------------------------------------------------------------------

void reshape(int w, int h) {

	window_size_w = w;
	window_size_h = h;
}
 
//-------------------------------------------------------------------------------

void projection(int &u, int &v, float x, float y, float z) {
	
//	glGetDoublev(GL_PROJECTION_MATRIX, projection);	
}
 
void mouse(int button, int state, int x, int y) {

    mouseX = x;
    mouseY = y;
    if ( state == GLUT_DOWN ) {
        switch( button ) {
        case GLUT_LEFT_BUTTON:
			mouseMode = MOUSE_MODE_VIEW_ROTATE;
            break;
		case GLUT_RIGHT_BUTTON:
            mouseMode = MOUSE_MODE_VIEW_ZOOM;
            break;
		case GLUT_MIDDLE_BUTTON:
			mouseMode = MOUSE_MODE_MOVE_LIGHT;
			break;
        }
    } else {
        mouseMode = MOUSE_MODE_NONE;
    }
    glutPostRedisplay();
}
 
//-------------------------------------------------------------------------------
 
#define VIEW_ROTATE_INC 0.04f
#define VIEW_ZOOM_INC 0.01f
#define LIGHT_MOVE_INC 0.01f
 
void mouseMove(int x, int y) {
 
    switch ( mouseMode ) {
    case MOUSE_MODE_VIEW_ROTATE:
        viewAngle1 -= scaleScene * VIEW_ROTATE_INC * ( mouseY - y );
        viewAngle2 -= scaleScene * VIEW_ROTATE_INC * ( mouseX - x );
        break;
    case MOUSE_MODE_VIEW_ZOOM:
        viewTransZ += scaleScene * VIEW_ZOOM_INC * ( mouseY - y );
        break;
	case MOUSE_MODE_MOVE_LIGHT:
		scene.light.eyey += scaleScene * LIGHT_MOVE_INC * (mouseY - y);
		scene.light.eyex -= scaleScene * LIGHT_MOVE_INC * (mouseX - x);
		break;
    }
 
    mouseX = x;
    mouseY = y; 
 
    glutPostRedisplay();
}

//-------------------------------------------------------------------------------

void keyboard(unsigned char key, int x , int y) {	

	switch(key) {

	case ' ':
		pauseCubeRotation = !pauseCubeRotation;
		break;

	case 'x':
	case 'X':
		cubeRotateX = 1;
		cubeRotateY = 0;
		cubeRotateZ = 0;
		break;
		
	case 'y':
	case 'Y':
		cubeRotateY = 1;
		cubeRotateX = 0;
		cubeRotateZ = 0;
		break;
		
	case 'z':
	case 'Z':
		cubeRotateZ = 1;
		cubeRotateY = 0;
		cubeRotateX = 0;
		break;

	case 'a':
	case 'A':
		if(sm_size > 64) {
			initShadowMap( sm_size - 64 );
			glutPostRedisplay();
		}
		break;

	case 'd':
	case 'D':
		if(sm_size < GL_MAX_TEXTURE_SIZE) {
			initShadowMap( sm_size + 64 );
			glutPostRedisplay();
		}
		break;

	case 's':
	case 'S': //press 's' or 'S', store the current image into ppm file
		GLint    viewPort[4] = {0};

		int arrLen = window_size_w * window_size_h * 3;
		unsigned char* colorArr_invs = new unsigned char[arrLen];
		unsigned char* colorArr = new unsigned char[arrLen];

		glGetIntegerv(GL_VIEWPORT, viewPort);
		glReadBuffer(GL_FRONT);

		glReadPixels(viewPort[0], viewPort[1], viewPort[2], viewPort[3], GL_RGB, GL_UNSIGNED_BYTE, colorArr_invs);
		for(int y = 0; y <viewPort[3]; y++)	{
            for(int x = 0; x <viewPort[2] * 3; x++) {
                    colorArr[(viewPort[3] - 1 - y) * viewPort[2] * 3 + x] = colorArr_invs[y * 3 * viewPort[2] + x];
            }
		}
		std::cout<<"read!"<<std::endl;
		char filename[10];
		sprintf(filename, "%d.ppm", sm_size);
		img::write_ppm(filename, colorArr, window_size_w, window_size_h);

		break;

	}

}